﻿using System;
using System.Security.Cryptography;
using System.Text;
using TSites.Utility.Random;

namespace TSites.Utility.Encrypt
{
	public class Encryptation : IEncryptation
	{
		private HashAlgorithm _hashProvider = null;

		private IRandom _byteRandom;
		private int _saltSize;

		public Encryptation(string hashAlgorithm, IRandom random, int size = 0)
		{
			_hashProvider = GetHashProvider(hashAlgorithm);
			_byteRandom = random;
			_saltSize = GetSaltSize(size);
		}

		#region Initialize
		
		private HashAlgorithm GetHashProvider(string hashAlgorithm)
		{
			HashAlgorithm hash;
			// Hashing algorithm
			hashAlgorithm = hashAlgorithm.ToUpper();
			switch (hashAlgorithm)
			{
				case "SHA1":
					hash = new SHA1Managed();
					break;

				case "SHA256":
					hash = new SHA256Managed();
					break;

				case "SHA384":
					hash = new SHA384Managed();
					break;

				case "SHA512":
					hash = new SHA512Managed();
					break;

				default:
					hash = new MD5CryptoServiceProvider();
					break;
			}
			return hash;
		}

		private int GetSaltSize(int size)
		{
			int saltSize = size;
			// if size is very small -> set its default value is 6
			if (saltSize > 0 && saltSize < 6)
			{
				saltSize = 6;
			}
			return saltSize;
		}

		#endregion

		#region Encryptation
		
		public string ComputeHash(string plainText, out string saltString)
		{
			byte[] salt = null;
			saltString = null;
			if (_saltSize > 0) // include salt
			{
				// Fill the salt with cryptographically strong byte values
				salt = _byteRandom.GetBytes(_saltSize);
				saltString = Convert.ToBase64String(salt);
			}

			return ComputeHash(plainText, salt);
		}

		private string ComputeHash(string plainText, byte[] salt)
		{
			// Convert plain text into a byte array
			byte[] data = Encoding.UTF8.GetBytes(plainText);
			int dataLength = data.Length;

			byte[] dataSalt;
			if (_saltSize > 0) // include salt
			{
				// Allocate memory to store both the Data and Salt together
				dataSalt = new byte[dataLength + _saltSize];

				// Copy both the data and salt into the new array
				Array.Copy(data, dataSalt, dataLength);
				Array.Copy(salt, 0, dataSalt, dataLength, _saltSize);
			}
			else
			{
				dataSalt = data;
			}

			// Calculate the hash
			// Compute hash value of our plain text with appended salt.
			byte[] encrypt = _hashProvider.ComputeHash(dataSalt);
			return Convert.ToBase64String(encrypt);
		}

		#endregion

		#region Verify hash

		public bool VerifyHash(string plainText, string saltString, string hashValue)
		{
			byte[] salt = null;
			if (_saltSize > 0)
			{
				salt = Convert.FromBase64String(saltString);
			}
			
			return string.Equals(hashValue, ComputeHash(plainText, salt));
		}

		#endregion
	}
}
